{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "whjsJasuhstV"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/jeffheaton/app_generative_ai/blob/main/t81_559_class_07_5_custom_agent.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "euOZxlIMhstX"
      },
      "source": [
        "# T81-559: Applications of Generative Artificial Intelligence\n",
        "**Module 7: LangChain: Agents**\n",
        "* Instructor: [Jeff Heaton](https://sites.wustl.edu/jeffheaton/), McKelvey School of Engineering, [Washington University in St. Louis](https://engineering.wustl.edu/Programs/Pages/default.aspx)\n",
        "* For more information visit the [class website](https://sites.wustl.edu/jeffheaton/t81-558/)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "d4Yov72PhstY"
      },
      "source": [
        "# Module 7 Material\n",
        "\n",
        "* Part 7.1: Introduction to LangChain Agents [[Video]](https://www.youtube.com/watch?v=J5Vr___lSSs) [[Notebook]](t81_559_class_07_1_agents.ipynb)\n",
        "* Part 7.2: Understanding LangChain Agent Tools [[Video]](https://www.youtube.com/watch?v=qMquBmteYw4) [[Notebook]](t81_559_class_07_2_tools.ipynb)\n",
        "* Part 7.3: LangChain Retrival and Search Tools [[Video]](https://www.youtube.com/watch?v=NB5qGPLoBBE) [[Notebook]](t81_559_class_07_3_search_tools.ipynb)\n",
        "* Part 7.4: Constructing LangChain Agents [[Video]](https://www.youtube.com/watch?v=OJe5oHvrdHk) [[Notebook]](t81_559_class_07_4_more_agent.ipynb)\n",
        "* **Part 7.5: Custom Agents** [[Video]](https://www.youtube.com/watch?v=IsJemVYSEdc) [[Notebook]](t81_559_class_07_5_custom_agent.ipynb)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "AcAUP0c3hstY"
      },
      "source": [
        "# Google CoLab Instructions\n",
        "\n",
        "The following code ensures that Google CoLab is running and maps Google Drive if needed."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "xsI496h5hstZ",
        "outputId": "163d13c0-51ad-450b-fbb3-bee8ba82dc22"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Note: using Google CoLab\n",
            "Collecting langchain\n",
            "  Downloading langchain-0.2.16-py3-none-any.whl.metadata (7.1 kB)\n",
            "Collecting langchain_openai\n",
            "  Downloading langchain_openai-0.1.23-py3-none-any.whl.metadata (2.6 kB)\n",
            "Collecting langchain_experimental\n",
            "  Downloading langchain_experimental-0.0.65-py3-none-any.whl.metadata (1.7 kB)\n",
            "Collecting langchainhub\n",
            "  Downloading langchainhub-0.1.21-py3-none-any.whl.metadata (659 bytes)\n",
            "Collecting sentence-transformers\n",
            "  Downloading sentence_transformers-3.0.1-py3-none-any.whl.metadata (10 kB)\n",
            "Requirement already satisfied: PyYAML>=5.3 in /usr/local/lib/python3.10/dist-packages (from langchain) (6.0.2)\n",
            "Requirement already satisfied: SQLAlchemy<3,>=1.4 in /usr/local/lib/python3.10/dist-packages (from langchain) (2.0.32)\n",
            "Requirement already satisfied: aiohttp<4.0.0,>=3.8.3 in /usr/local/lib/python3.10/dist-packages (from langchain) (3.10.5)\n",
            "Requirement already satisfied: async-timeout<5.0.0,>=4.0.0 in /usr/local/lib/python3.10/dist-packages (from langchain) (4.0.3)\n",
            "Collecting langchain-core<0.3.0,>=0.2.38 (from langchain)\n",
            "  Downloading langchain_core-0.2.38-py3-none-any.whl.metadata (6.2 kB)\n",
            "Collecting langchain-text-splitters<0.3.0,>=0.2.0 (from langchain)\n",
            "  Downloading langchain_text_splitters-0.2.4-py3-none-any.whl.metadata (2.3 kB)\n",
            "Collecting langsmith<0.2.0,>=0.1.17 (from langchain)\n",
            "  Downloading langsmith-0.1.116-py3-none-any.whl.metadata (13 kB)\n",
            "Requirement already satisfied: numpy<2,>=1 in /usr/local/lib/python3.10/dist-packages (from langchain) (1.26.4)\n",
            "Requirement already satisfied: pydantic<3,>=1 in /usr/local/lib/python3.10/dist-packages (from langchain) (2.8.2)\n",
            "Requirement already satisfied: requests<3,>=2 in /usr/local/lib/python3.10/dist-packages (from langchain) (2.32.3)\n",
            "Collecting tenacity!=8.4.0,<9.0.0,>=8.1.0 (from langchain)\n",
            "  Downloading tenacity-8.5.0-py3-none-any.whl.metadata (1.2 kB)\n",
            "Collecting openai<2.0.0,>=1.40.0 (from langchain_openai)\n",
            "  Downloading openai-1.44.0-py3-none-any.whl.metadata (22 kB)\n",
            "Collecting tiktoken<1,>=0.7 (from langchain_openai)\n",
            "  Downloading tiktoken-0.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.6 kB)\n",
            "Collecting langchain-community<0.3.0,>=0.2.16 (from langchain_experimental)\n",
            "  Downloading langchain_community-0.2.16-py3-none-any.whl.metadata (2.7 kB)\n",
            "Requirement already satisfied: packaging<25,>=23.2 in /usr/local/lib/python3.10/dist-packages (from langchainhub) (24.1)\n",
            "Collecting types-requests<3.0.0.0,>=2.31.0.2 (from langchainhub)\n",
            "  Downloading types_requests-2.32.0.20240907-py3-none-any.whl.metadata (1.9 kB)\n",
            "Requirement already satisfied: transformers<5.0.0,>=4.34.0 in /usr/local/lib/python3.10/dist-packages (from sentence-transformers) (4.44.2)\n",
            "Requirement already satisfied: tqdm in /usr/local/lib/python3.10/dist-packages (from sentence-transformers) (4.66.5)\n",
            "Requirement already satisfied: torch>=1.11.0 in /usr/local/lib/python3.10/dist-packages (from sentence-transformers) (2.4.0+cu121)\n",
            "Requirement already satisfied: scikit-learn in /usr/local/lib/python3.10/dist-packages (from sentence-transformers) (1.3.2)\n",
            "Requirement already satisfied: scipy in /usr/local/lib/python3.10/dist-packages (from sentence-transformers) (1.13.1)\n",
            "Requirement already satisfied: huggingface-hub>=0.15.1 in /usr/local/lib/python3.10/dist-packages (from sentence-transformers) (0.24.6)\n",
            "Requirement already satisfied: Pillow in /usr/local/lib/python3.10/dist-packages (from sentence-transformers) (9.4.0)\n",
            "Requirement already satisfied: aiohappyeyeballs>=2.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (2.4.0)\n",
            "Requirement already satisfied: aiosignal>=1.1.2 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.3.1)\n",
            "Requirement already satisfied: attrs>=17.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (24.2.0)\n",
            "Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.4.1)\n",
            "Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (6.0.5)\n",
            "Requirement already satisfied: yarl<2.0,>=1.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.9.4)\n",
            "Requirement already satisfied: filelock in /usr/local/lib/python3.10/dist-packages (from huggingface-hub>=0.15.1->sentence-transformers) (3.15.4)\n",
            "Requirement already satisfied: fsspec>=2023.5.0 in /usr/local/lib/python3.10/dist-packages (from huggingface-hub>=0.15.1->sentence-transformers) (2024.6.1)\n",
            "Requirement already satisfied: typing-extensions>=3.7.4.3 in /usr/local/lib/python3.10/dist-packages (from huggingface-hub>=0.15.1->sentence-transformers) (4.12.2)\n",
            "Collecting dataclasses-json<0.7,>=0.5.7 (from langchain-community<0.3.0,>=0.2.16->langchain_experimental)\n",
            "  Downloading dataclasses_json-0.6.7-py3-none-any.whl.metadata (25 kB)\n",
            "Collecting jsonpatch<2.0,>=1.33 (from langchain-core<0.3.0,>=0.2.38->langchain)\n",
            "  Downloading jsonpatch-1.33-py2.py3-none-any.whl.metadata (3.0 kB)\n",
            "Collecting httpx<1,>=0.23.0 (from langsmith<0.2.0,>=0.1.17->langchain)\n",
            "  Downloading httpx-0.27.2-py3-none-any.whl.metadata (7.1 kB)\n",
            "Collecting orjson<4.0.0,>=3.9.14 (from langsmith<0.2.0,>=0.1.17->langchain)\n",
            "  Downloading orjson-3.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (50 kB)\n",
            "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m50.4/50.4 kB\u001b[0m \u001b[31m934.4 kB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hRequirement already satisfied: anyio<5,>=3.5.0 in /usr/local/lib/python3.10/dist-packages (from openai<2.0.0,>=1.40.0->langchain_openai) (3.7.1)\n",
            "Requirement already satisfied: distro<2,>=1.7.0 in /usr/lib/python3/dist-packages (from openai<2.0.0,>=1.40.0->langchain_openai) (1.7.0)\n",
            "Collecting jiter<1,>=0.4.0 (from openai<2.0.0,>=1.40.0->langchain_openai)\n",
            "  Downloading jiter-0.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (3.6 kB)\n",
            "Requirement already satisfied: sniffio in /usr/local/lib/python3.10/dist-packages (from openai<2.0.0,>=1.40.0->langchain_openai) (1.3.1)\n",
            "Requirement already satisfied: annotated-types>=0.4.0 in /usr/local/lib/python3.10/dist-packages (from pydantic<3,>=1->langchain) (0.7.0)\n",
            "Requirement already satisfied: pydantic-core==2.20.1 in /usr/local/lib/python3.10/dist-packages (from pydantic<3,>=1->langchain) (2.20.1)\n",
            "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain) (3.3.2)\n",
            "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain) (3.8)\n",
            "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain) (2.0.7)\n",
            "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain) (2024.8.30)\n",
            "Requirement already satisfied: greenlet!=0.4.17 in /usr/local/lib/python3.10/dist-packages (from SQLAlchemy<3,>=1.4->langchain) (3.0.3)\n",
            "Requirement already satisfied: regex>=2022.1.18 in /usr/local/lib/python3.10/dist-packages (from tiktoken<1,>=0.7->langchain_openai) (2024.5.15)\n",
            "Requirement already satisfied: sympy in /usr/local/lib/python3.10/dist-packages (from torch>=1.11.0->sentence-transformers) (1.13.2)\n",
            "Requirement already satisfied: networkx in /usr/local/lib/python3.10/dist-packages (from torch>=1.11.0->sentence-transformers) (3.3)\n",
            "Requirement already satisfied: jinja2 in /usr/local/lib/python3.10/dist-packages (from torch>=1.11.0->sentence-transformers) (3.1.4)\n",
            "Requirement already satisfied: safetensors>=0.4.1 in /usr/local/lib/python3.10/dist-packages (from transformers<5.0.0,>=4.34.0->sentence-transformers) (0.4.4)\n",
            "Requirement already satisfied: tokenizers<0.20,>=0.19 in /usr/local/lib/python3.10/dist-packages (from transformers<5.0.0,>=4.34.0->sentence-transformers) (0.19.1)\n",
            "Requirement already satisfied: joblib>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from scikit-learn->sentence-transformers) (1.4.2)\n",
            "Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.10/dist-packages (from scikit-learn->sentence-transformers) (3.5.0)\n",
            "Requirement already satisfied: exceptiongroup in /usr/local/lib/python3.10/dist-packages (from anyio<5,>=3.5.0->openai<2.0.0,>=1.40.0->langchain_openai) (1.2.2)\n",
            "Collecting marshmallow<4.0.0,>=3.18.0 (from dataclasses-json<0.7,>=0.5.7->langchain-community<0.3.0,>=0.2.16->langchain_experimental)\n",
            "  Downloading marshmallow-3.22.0-py3-none-any.whl.metadata (7.2 kB)\n",
            "Collecting typing-inspect<1,>=0.4.0 (from dataclasses-json<0.7,>=0.5.7->langchain-community<0.3.0,>=0.2.16->langchain_experimental)\n",
            "  Downloading typing_inspect-0.9.0-py3-none-any.whl.metadata (1.5 kB)\n",
            "Collecting httpcore==1.* (from httpx<1,>=0.23.0->langsmith<0.2.0,>=0.1.17->langchain)\n",
            "  Downloading httpcore-1.0.5-py3-none-any.whl.metadata (20 kB)\n",
            "Collecting h11<0.15,>=0.13 (from httpcore==1.*->httpx<1,>=0.23.0->langsmith<0.2.0,>=0.1.17->langchain)\n",
            "  Downloading h11-0.14.0-py3-none-any.whl.metadata (8.2 kB)\n",
            "Collecting jsonpointer>=1.9 (from jsonpatch<2.0,>=1.33->langchain-core<0.3.0,>=0.2.38->langchain)\n",
            "  Downloading jsonpointer-3.0.0-py2.py3-none-any.whl.metadata (2.3 kB)\n",
            "Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.10/dist-packages (from jinja2->torch>=1.11.0->sentence-transformers) (2.1.5)\n",
            "Requirement already satisfied: mpmath<1.4,>=1.1.0 in /usr/local/lib/python3.10/dist-packages (from sympy->torch>=1.11.0->sentence-transformers) (1.3.0)\n",
            "Collecting mypy-extensions>=0.3.0 (from typing-inspect<1,>=0.4.0->dataclasses-json<0.7,>=0.5.7->langchain-community<0.3.0,>=0.2.16->langchain_experimental)\n",
            "  Downloading mypy_extensions-1.0.0-py3-none-any.whl.metadata (1.1 kB)\n",
            "Downloading langchain-0.2.16-py3-none-any.whl (1.0 MB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.0/1.0 MB\u001b[0m \u001b[31m19.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading langchain_openai-0.1.23-py3-none-any.whl (51 kB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m52.0/52.0 kB\u001b[0m \u001b[31m3.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading langchain_experimental-0.0.65-py3-none-any.whl (207 kB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m207.2/207.2 kB\u001b[0m \u001b[31m13.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading langchainhub-0.1.21-py3-none-any.whl (5.2 kB)\n",
            "Downloading sentence_transformers-3.0.1-py3-none-any.whl (227 kB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m227.1/227.1 kB\u001b[0m \u001b[31m13.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading langchain_community-0.2.16-py3-none-any.whl (2.3 MB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m2.3/2.3 MB\u001b[0m \u001b[31m58.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading langchain_core-0.2.38-py3-none-any.whl (396 kB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m396.4/396.4 kB\u001b[0m \u001b[31m18.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading langchain_text_splitters-0.2.4-py3-none-any.whl (25 kB)\n",
            "Downloading langsmith-0.1.116-py3-none-any.whl (290 kB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m290.4/290.4 kB\u001b[0m \u001b[31m17.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading openai-1.44.0-py3-none-any.whl (367 kB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m367.8/367.8 kB\u001b[0m \u001b[31m19.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading tenacity-8.5.0-py3-none-any.whl (28 kB)\n",
            "Downloading tiktoken-0.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.1 MB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.1/1.1 MB\u001b[0m \u001b[31m35.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading types_requests-2.32.0.20240907-py3-none-any.whl (15 kB)\n",
            "Downloading dataclasses_json-0.6.7-py3-none-any.whl (28 kB)\n",
            "Downloading httpx-0.27.2-py3-none-any.whl (76 kB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m76.4/76.4 kB\u001b[0m \u001b[31m6.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading httpcore-1.0.5-py3-none-any.whl (77 kB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m77.9/77.9 kB\u001b[0m \u001b[31m5.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading jiter-0.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (318 kB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m318.9/318.9 kB\u001b[0m \u001b[31m17.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading jsonpatch-1.33-py2.py3-none-any.whl (12 kB)\n",
            "Downloading orjson-3.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (141 kB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m141.9/141.9 kB\u001b[0m \u001b[31m8.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading jsonpointer-3.0.0-py2.py3-none-any.whl (7.6 kB)\n",
            "Downloading marshmallow-3.22.0-py3-none-any.whl (49 kB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m49.3/49.3 kB\u001b[0m \u001b[31m3.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading typing_inspect-0.9.0-py3-none-any.whl (8.8 kB)\n",
            "Downloading h11-0.14.0-py3-none-any.whl (58 kB)\n",
            "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m58.3/58.3 kB\u001b[0m \u001b[31m4.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
            "\u001b[?25hDownloading mypy_extensions-1.0.0-py3-none-any.whl (4.7 kB)\n",
            "Installing collected packages: types-requests, tenacity, orjson, mypy-extensions, marshmallow, jsonpointer, jiter, h11, typing-inspect, tiktoken, langchainhub, jsonpatch, httpcore, httpx, dataclasses-json, openai, langsmith, sentence-transformers, langchain-core, langchain-text-splitters, langchain_openai, langchain, langchain-community, langchain_experimental\n",
            "  Attempting uninstall: tenacity\n",
            "    Found existing installation: tenacity 9.0.0\n",
            "    Uninstalling tenacity-9.0.0:\n",
            "      Successfully uninstalled tenacity-9.0.0\n",
            "Successfully installed dataclasses-json-0.6.7 h11-0.14.0 httpcore-1.0.5 httpx-0.27.2 jiter-0.5.0 jsonpatch-1.33 jsonpointer-3.0.0 langchain-0.2.16 langchain-community-0.2.16 langchain-core-0.2.38 langchain-text-splitters-0.2.4 langchain_experimental-0.0.65 langchain_openai-0.1.23 langchainhub-0.1.21 langsmith-0.1.116 marshmallow-3.22.0 mypy-extensions-1.0.0 openai-1.44.0 orjson-3.10.7 sentence-transformers-3.0.1 tenacity-8.5.0 tiktoken-0.7.0 types-requests-2.32.0.20240907 typing-inspect-0.9.0\n"
          ]
        }
      ],
      "source": [
        "import os\n",
        "\n",
        "try:\n",
        "    from google.colab import drive, userdata\n",
        "    COLAB = True\n",
        "    print(\"Note: using Google CoLab\")\n",
        "except:\n",
        "    print(\"Note: not using Google CoLab\")\n",
        "    COLAB = False\n",
        "\n",
        "# OpenAI Secrets\n",
        "if COLAB:\n",
        "    os.environ[\"OPENAI_API_KEY\"] = userdata.get('OPENAI_API_KEY')\n",
        "\n",
        "\n",
        "# Install needed libraries in CoLab\n",
        "if COLAB:\n",
        "    !pip install langchain langchain_openai langchain_experimental langchainhub sentence-transformers"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pC9A-LaYhsta"
      },
      "source": [
        "# 7.5: Custom LangChain Agents\n",
        "\n",
        "In this section, we delve into the creation of custom agents within LangChain. By designing bespoke tools, we can enhance the functionality of our agents to tackle more intricate tasks. These custom agents empower us to tailor solutions to specific requirements, enabling more sophisticated interactions and problem-solving capabilities. By leveraging LangChain's flexible framework, we can seamlessly integrate these custom tools to address unique challenges and optimize performance, providing a more robust and adaptable solution for complex scenarios.\n",
        "\n",
        "## A Simple Custom Tool\n",
        "\n",
        "We begin with a very simple tool: a button for the Language Model (LLM) to push. By providing this button, we can observe and confirm that the LLM can indeed interact with it. This fundamental interaction serves as a foundational step, demonstrating the basic capabilities of the LLM to execute commands. The following code constructs an LLM, as we have done previously, allowing us to build upon this basic interaction and progressively incorporate more complex tools and functionalities."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "metadata": {
        "id": "_RvXin2X3O0K"
      },
      "outputs": [],
      "source": [
        "from langchain_openai import ChatOpenAI\n",
        "\n",
        "MODEL = 'gpt-4o-mini'\n",
        "\n",
        "llm = ChatOpenAI(\n",
        "        model=MODEL,\n",
        "        temperature=0.2,\n",
        "        n=1\n",
        "    )\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "x4tbtkpECGyD"
      },
      "source": [
        "In the provided code, we create a simple agent tool designed to press a button. We begin by importing the Tool class from the LangChain agents module. The core functionality is defined in the press_button function, which takes a single argument, value. When this function is called, it prints the provided value and returns the string \"The button glows red!\".\n",
        "\n",
        "Next, we instantiate a Tool object named button_tool. We specify its name as \"button_machine\" and provide a description that explains its purpose: it is a big red button that can be pushed, and it accepts a single value when prompted. The func parameter is set to our press_button function, linking the tool's action to our defined behavior. This setup allows the LLM to interact with the button tool by pushing it and observing the corresponding response."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "metadata": {
        "id": "dTylRLZJ4fZc"
      },
      "outputs": [],
      "source": [
        "from langchain.agents import Tool\n",
        "\n",
        "def press_button(value):\n",
        "  print(f\"*************{value}\")\n",
        "  return \"The button glows red!\"\n",
        "\n",
        "button_tool = Tool(\n",
        "    name=\"button_machine\",\n",
        "    description=\"A big red button that you can push. You can send a single value to the button, if asked. \",\n",
        "    func=press_button,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "TcT-dmNBCbNy"
      },
      "source": [
        "In this code, we construct an agent and instruct it to press the button we previously defined. We start by importing the necessary modules and functions, including hub from LangChain, create_tool_calling_agent, and AgentExecutor. We then pull a prompt from the LangChain hub, which serves as the template for our agent's interactions.\n",
        "\n",
        "We define a list named tools, which includes our previously created button_tool. Using the create_tool_calling_agent function, we create an agent that can utilize the tools we've specified, guided by the pulled prompt. The AgentExecutor class is then used to instantiate an agent_executor, linking the agent with the tools and enabling verbose output for better insight into the interactions.\n",
        "\n",
        "Finally, we invoke the agent with the input command: \"Push the big red button, tell me what happens.\" This command instructs the agent to use the button tool, demonstrating the agent's ability to perform the defined action and providing feedback on the outcome."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "ZlU1rtdH4gJv",
        "outputId": "c89ab740-d983-4e57-e60c-0fa2f3761321"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "/usr/local/lib/python3.10/dist-packages/langsmith/client.py:312: LangSmithMissingAPIKeyWarning: API key must be provided when using hosted LangSmith API\n",
            "  warnings.warn(\n",
            "/usr/local/lib/python3.10/dist-packages/langsmith/client.py:5515: LangChainBetaWarning: The function `loads` is in beta. It is actively being worked on, so the API may change.\n",
            "  prompt = loads(json.dumps(prompt_object.manifest))\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\n",
            "\n",
            "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
            "\u001b[32;1m\u001b[1;3m\n",
            "Invoking: `button_machine` with `{'config': {'tags': ['big red button'], 'run_name': 'Push the Big Red Button'}}`\n",
            "\n",
            "\n",
            "\u001b[0m*************{'tags': ['big red button'], 'run_name': 'Push the Big Red Button'}\n",
            "\u001b[36;1m\u001b[1;3mThe button glows red!\u001b[0m\u001b[32;1m\u001b[1;3mWhen you push the big red button, it glows red!\u001b[0m\n",
            "\n",
            "\u001b[1m> Finished chain.\u001b[0m\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "{'input': 'Push the big red button, tell me what happens.',\n",
              " 'output': 'When you push the big red button, it glows red!'}"
            ]
          },
          "metadata": {},
          "execution_count": 4
        }
      ],
      "source": [
        "from langchain import hub\n",
        "from langchain.agents import create_tool_calling_agent\n",
        "from langchain_community.tools import DuckDuckGoSearchRun\n",
        "from langchain.agents import AgentExecutor\n",
        "\n",
        "prompt = hub.pull(\"hwchase17/openai-functions-agent\")\n",
        "\n",
        "tools = [button_tool]\n",
        "agent = create_tool_calling_agent(llm, tools, prompt)\n",
        "agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)\n",
        "agent_executor.invoke({\"input\": \"Push the big red button, tell me what happens.\"})"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "R6nAGgIY3FFA"
      },
      "source": [
        "## Controling a Simple Robot with Tools\n",
        "\n",
        "\n",
        "In this code, we create a more complex tool involving three buttons that control the movement of a small car. We define a CarTool class that initializes the car's position (x, y) and direction (north, east, south, west). The class also creates a Tool object named control_panel with a description explaining the functions of three buttons: red, green, and yellow.\n",
        "\n",
        "The CarTool class includes methods to move the car forward (move_forward), turn it left (turn_left), and turn it right (turn_right). The press_button method processes button presses by color. Pressing the red button moves the car forward by one unit in its current direction. Pressing the green button turns the car 90 degrees to the left, and pressing the yellow button turns the car 90 degrees to the right. If an invalid button color is pressed, an error message is returned.\n",
        "\n",
        "Additionally, the get_position method provides the current position and direction of the car. The example usage at the end demonstrates how to use the CarTool to control the car by pressing different buttons and handling invalid input."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "tol8GOGD87U3",
        "outputId": "bf20d698-b5d6-4534-a943-3429baf9ab77"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Current car position: (0, 1, 'north')\n",
            "The button glows red, you move forward one unit.\n",
            "Current car position: (0, 1, 'west')\n",
            "The button glows green, you turn 90 degrees to the left.\n",
            "Current car position: (0, 1, 'north')\n",
            "The button glows yellow, you turn 90 degrees to the right.\n",
            "Current car position: (0, 1, 'north')\n",
            "The button buzzes, error.\n"
          ]
        }
      ],
      "source": [
        "from langchain.tools import StructuredTool\n",
        "from pydantic import BaseModel\n",
        "from langchain import hub\n",
        "from langchain_openai import ChatOpenAI\n",
        "from langchain.agents import create_tool_calling_agent\n",
        "from langchain.agents import AgentExecutor\n",
        "\n",
        "# Define a Pydantic schema for the button press input\n",
        "class ButtonPressInput(BaseModel):\n",
        "    button_color: str\n",
        "\n",
        "class CarTool:\n",
        "    def __init__(self):\n",
        "        self.x = 0\n",
        "        self.y = 0\n",
        "        self.direction = 'north'\n",
        "        self.directions = ['north', 'east', 'south', 'west']\n",
        "        self.tool = StructuredTool(\n",
        "            name=\"control_panel\",\n",
        "            description=\"\"\"Three buttons, red, green and yellow that you can push.\n",
        "            The red button moves you forward 1 unit.\n",
        "            The green button turns left 90 degrees,\n",
        "            the yellow button turns right 90 degrees.\"\"\",\n",
        "            func=self.press_button,\n",
        "            args_schema=ButtonPressInput  # Define the expected input schema\n",
        "        )\n",
        "\n",
        "    def move_forward(self):\n",
        "        if self.direction == 'north':\n",
        "            self.y += 1\n",
        "        elif self.direction == 'east':\n",
        "            self.x += 1\n",
        "        elif self.direction == 'south':\n",
        "            self.y -= 1\n",
        "        elif self.direction == 'west':\n",
        "            self.x -= 1\n",
        "\n",
        "    def turn_left(self):\n",
        "        current_index = self.directions.index(self.direction)\n",
        "        self.direction = self.directions[(current_index - 1) % 4]\n",
        "\n",
        "    def turn_right(self):\n",
        "        current_index = self.directions.index(self.direction)\n",
        "        self.direction = self.directions[(current_index + 1) % 4]\n",
        "\n",
        "    def press_button(self, button_color: str):\n",
        "        button_color = button_color.strip().lower()\n",
        "        if button_color == 'red':\n",
        "            self.move_forward()\n",
        "            result = \"The button glows red, you move forward one unit.\"\n",
        "        elif button_color == 'green':\n",
        "            self.turn_left()\n",
        "            result = \"The button glows green, you turn 90 degrees to the left.\"\n",
        "        elif button_color == 'yellow':\n",
        "            self.turn_right()\n",
        "            result = \"The button glows yellow, you turn 90 degrees to the right.\"\n",
        "        else:\n",
        "            result = \"The button buzzes, error.\"\n",
        "\n",
        "        print(f\"Current car position: {self.get_position()}\")\n",
        "        return result\n",
        "\n",
        "    def get_position(self):\n",
        "        return self.x, self.y, self.direction\n",
        "\n",
        "# Example usage\n",
        "car_tool = CarTool()\n",
        "\n",
        "try:\n",
        "    print(car_tool.tool.func('red'))  # Move forward\n",
        "    print(car_tool.tool.func('green'))  # Turn left\n",
        "    print(car_tool.tool.func('yellow'))  # Turn right\n",
        "    print(car_tool.tool.func('blue'))  # Invalid button color, raises ValueError\n",
        "except ValueError as e:\n",
        "    print(e)\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Vi3Aa0EvC_AW"
      },
      "source": [
        "We now construct an agent and provide this tool, we request the car to move in a rectangle."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "oHVfoXVcGGHK",
        "outputId": "8537e404-2855-4e55-8e67-1138b766b2b9"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "/usr/local/lib/python3.10/dist-packages/langsmith/client.py:312: LangSmithMissingAPIKeyWarning: API key must be provided when using hosted LangSmith API\n",
            "  warnings.warn(\n"
          ]
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "\n",
            "\n",
            "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
            "\u001b[32;1m\u001b[1;3m\n",
            "Invoking: `control_panel` with `{'button_color': 'red'}`\n",
            "\n",
            "\n",
            "\u001b[0mCurrent car position: (0, 2, 'north')\n",
            "\u001b[36;1m\u001b[1;3mThe button glows red, you move forward one unit.\u001b[0m\u001b[32;1m\u001b[1;3m\n",
            "Invoking: `control_panel` with `{'button_color': 'green'}`\n",
            "\n",
            "\n",
            "\u001b[0mCurrent car position: (0, 2, 'west')\n",
            "\u001b[36;1m\u001b[1;3mThe button glows green, you turn 90 degrees to the left.\u001b[0m\u001b[32;1m\u001b[1;3m\n",
            "Invoking: `control_panel` with `{'button_color': 'red'}`\n",
            "\n",
            "\n",
            "\u001b[0mCurrent car position: (-1, 2, 'west')\n",
            "\u001b[36;1m\u001b[1;3mThe button glows red, you move forward one unit.\u001b[0m\u001b[32;1m\u001b[1;3m\n",
            "Invoking: `control_panel` with `{'button_color': 'green'}`\n",
            "\n",
            "\n",
            "\u001b[0mCurrent car position: (-1, 2, 'south')\n",
            "\u001b[36;1m\u001b[1;3mThe button glows green, you turn 90 degrees to the left.\u001b[0m\u001b[32;1m\u001b[1;3m\n",
            "Invoking: `control_panel` with `{'button_color': 'red'}`\n",
            "\n",
            "\n",
            "\u001b[0mCurrent car position: (-1, 1, 'south')\n",
            "\u001b[36;1m\u001b[1;3mThe button glows red, you move forward one unit.\u001b[0m\u001b[32;1m\u001b[1;3m\n",
            "Invoking: `control_panel` with `{'button_color': 'green'}`\n",
            "\n",
            "\n",
            "\u001b[0mCurrent car position: (-1, 1, 'east')\n",
            "\u001b[36;1m\u001b[1;3mThe button glows green, you turn 90 degrees to the left.\u001b[0m\u001b[32;1m\u001b[1;3m\n",
            "Invoking: `control_panel` with `{'button_color': 'red'}`\n",
            "\n",
            "\n",
            "\u001b[0mCurrent car position: (0, 1, 'east')\n",
            "\u001b[36;1m\u001b[1;3mThe button glows red, you move forward one unit.\u001b[0m\u001b[32;1m\u001b[1;3m\n",
            "Invoking: `control_panel` with `{'button_color': 'green'}`\n",
            "\n",
            "\n",
            "\u001b[0mCurrent car position: (0, 1, 'north')\n",
            "\u001b[36;1m\u001b[1;3mThe button glows green, you turn 90 degrees to the left.\u001b[0m\u001b[32;1m\u001b[1;3mThe car has moved in a rectangle as follows:\n",
            "\n",
            "1. Moved forward 1 unit.\n",
            "2. Turned 90 degrees to the left.\n",
            "3. Moved forward 1 unit.\n",
            "4. Turned 90 degrees to the left.\n",
            "5. Moved forward 1 unit.\n",
            "6. Turned 90 degrees to the left.\n",
            "7. Moved forward 1 unit.\n",
            "8. Turned 90 degrees to the left.\n",
            "\n",
            "The car is now back at its starting position, having completed a rectangular path.\u001b[0m\n",
            "\n",
            "\u001b[1m> Finished chain.\u001b[0m\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "{'input': 'Push the buttons in a way that causes the car to move in a rectangle.',\n",
              " 'output': 'The car has moved in a rectangle as follows:\\n\\n1. Moved forward 1 unit.\\n2. Turned 90 degrees to the left.\\n3. Moved forward 1 unit.\\n4. Turned 90 degrees to the left.\\n5. Moved forward 1 unit.\\n6. Turned 90 degrees to the left.\\n7. Moved forward 1 unit.\\n8. Turned 90 degrees to the left.\\n\\nThe car is now back at its starting position, having completed a rectangular path.'}"
            ]
          },
          "metadata": {},
          "execution_count": 6
        }
      ],
      "source": [
        "# Initialize LLM and create an agent\n",
        "MODEL = 'gpt-4o-mini'\n",
        "llm = ChatOpenAI(model=MODEL, temperature=0.2, n=1)\n",
        "\n",
        "prompt = hub.pull(\"hwchase17/openai-functions-agent\")\n",
        "\n",
        "tools = [car_tool.tool]\n",
        "agent = create_tool_calling_agent(llm, tools, prompt)\n",
        "agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)\n",
        "\n",
        "# Invoke the agent to control the car\n",
        "agent_executor.invoke({\"input\": \"Push the buttons in a way that causes the car to move in a rectangle.\"})\n",
        "\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "source": [],
      "metadata": {
        "id": "6lsC9cNjV8cM"
      },
      "execution_count": 6,
      "outputs": []
    }
  ],
  "metadata": {
    "anaconda-cloud": {},
    "colab": {
      "provenance": []
    },
    "kernelspec": {
      "display_name": "Python 3.11 (genai)",
      "language": "python",
      "name": "genai"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.11.8"
    },
    "varInspector": {
      "cols": {
        "lenName": 16,
        "lenType": 16,
        "lenVar": 40
      },
      "kernels_config": {
        "python": {
          "delete_cmd_postfix": "",
          "delete_cmd_prefix": "del ",
          "library": "var_list.py",
          "varRefreshCmd": "print(var_dic_list())"
        },
        "r": {
          "delete_cmd_postfix": ") ",
          "delete_cmd_prefix": "rm(",
          "library": "var_list.r",
          "varRefreshCmd": "cat(var_dic_list()) "
        }
      },
      "types_to_exclude": [
        "module",
        "function",
        "builtin_function_or_method",
        "instance",
        "_Feature"
      ],
      "window_display": false
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}